package sc.digital.human;

import com.google.gson.Gson;
import okhttp3.*;
import okio.ByteString;

import javax.swing.*;
import java.awt.*;
import java.util.UUID;

/**
 * <a href="https://ai.baidu.com/tech/speech/realtime_asr">实时语音识别</a>
 */
public class AsrStreaming extends JFrame {
    JTextArea talking;
    JLabel speaking;
    JToggleButton saying;
    Setup setup = new Setup(this);
    Gson gson = new Gson();
    Audio audio = new Audio(new byte[5120]);
    OkHttpClient httpClient = new OkHttpClient.Builder().build();
    WebSocket webSocket;

    public AsrStreaming() {
        super.setTitle("语音识别");
        super.setIconImage(Config.LOGO);
        super.setDefaultCloseOperation(EXIT_ON_CLOSE);
        super.setLayout(new BorderLayout());
        super.add(new JScrollPane(talking = new JTextArea(10, 50) {{
            this.setEditable(false);
            this.setFocusable(false);
        }}), BorderLayout.CENTER);
        super.add(new JPanel() {{
            this.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
            this.setLayout(new BorderLayout());
            this.add(new JButton("设置") {{
                this.setFocusable(false);
                this.setFocusPainted(false);
                this.addActionListener((e) -> setup.setVisible(true));
            }}, BorderLayout.WEST);
            this.add(speaking = new JLabel() {{
                this.setFocusable(false);
            }}, BorderLayout.CENTER);
            this.add(saying = new JToggleButton("按下 说话") {{
                this.setFocusable(false);
                this.setFocusPainted(false);
                this.addActionListener((e) -> {
                    if (this.isSelected()) {
                        webSocket = httpClient.newWebSocket(new Request.Builder()
                                .url(String.format("wss://vop.baidu.com/realtime_asr?sn=%s", UUID.randomUUID())).build(), listener);
                    } else {
                        webSocket.send(gson.toJson(new Asr(AsrType.FINISH)));
                        webSocket.send(gson.toJson(new Asr(AsrType.CANCEL)));
                    }
                    this.setText(this.isSelected() ? "按下 停止" : "按下 说话");
                });
            }}, BorderLayout.EAST);
        }}, BorderLayout.SOUTH);
        super.pack();
        super.setLocationRelativeTo(null);
    }

    void startSaying() {
        this.webSocket.send(gson.toJson(new Asr(AsrType.START)));
        this.audio.startRecording((buffer, offset, length) -> this.webSocket.send(ByteString.of(buffer, offset, length)), null);
    }

    void stopSaying(String msg) {
        this.audio.stopRecording();
        SwingUtilities.invokeLater(() -> {
            this.saying.setSelected(false);
            this.saying.setText("按下 说话");
            if (msg != null && !msg.isEmpty()) {
                JOptionPane.showMessageDialog(this, msg, "", JOptionPane.PLAIN_MESSAGE);
            }
        });
    }

    void saying(Asr asr) {
        if (asr.err_no != 0) {
            this.stopSaying(String.format("%s(%s)", asr.err_no, asr.err_msg));
        } else {
            SwingUtilities.invokeLater(() -> {
                if (AsrType.MID_TEXT == asr.type || AsrType.FIN_TEXT == asr.type) {
                    this.speaking.setText(asr.result == null || asr.result.isEmpty() ? "" : asr.result);
                }
                if (AsrType.FIN_TEXT == asr.type && !this.speaking.getText().isEmpty()) {
                    this.talking.append(this.speaking.getText() + "\r\n");
                }
            });
        }
    }

    WebSocketListener listener = new WebSocketListener() {
        @Override
        public void onOpen(WebSocket webSocket, Response response) {
            System.out.printf("实时语音识别\t%s%n", response.toString());
            startSaying();
        }

        @Override
        public void onMessage(WebSocket webSocket, String text) {
            System.out.printf("实时语音识别\t%s%n", text);
            saying(gson.fromJson(text, Asr.class));
        }

        @Override
        public void onMessage(WebSocket webSocket, ByteString bytes) {
            System.out.printf("实时语音识别\t%s%n", bytes.toString());
        }

        @Override
        public void onClosing(WebSocket webSocket, int code, String reason) {
            System.out.printf("实时语音识别\t服务端关闭(%s:%s)%n", code, reason);
            webSocket.close(1000, "");
        }

        @Override
        public void onClosed(WebSocket webSocket, int code, String reason) {
            System.out.printf("实时语音识别\t客户端关闭(%s:%s)%n", code, reason);
            stopSaying(null);
        }

        @Override
        public void onFailure(WebSocket webSocket, Throwable t, Response response) {
            System.err.printf("实时语音识别\t异常(%s)%n", t.toString());
            stopSaying(String.format("异常(%s)", t.getMessage()));
        }
    };

    static class Asr {
        public int err_no;
        public String err_msg;
        public AsrType type;
        public String result;
        public Data data;

        public Asr() {
        }

        public Asr(AsrType type) {
            this();
            this.type = type;
            if (AsrType.START == type) {
                this.data = new Data();
            }
        }

        static class Data {
            public int appid;
            public String appkey;
            public int dev_pid;
            public String cuid;
            public String format;
            public int sample;

            public Data() {
                this.appid = Config.APP_ID;
                this.appkey = Config.APP_KEY;
                this.dev_pid = 15372;
                this.cuid = "SC1234567890";
                this.format = "pcm";
                this.sample = Config.SAMPLE_RATE;
            }
        }
    }

    enum AsrType {
        START, FINISH, CANCEL, MID_TEXT, FIN_TEXT
    }

    public static void main(String[] args) throws Exception {
        Config.readConfig();
        Config.readCmdlineArgs(args);
        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        SwingUtilities.invokeLater(() -> new AsrStreaming().setVisible(true));
    }
}
